/*!
 * # Semantic UI - Visibility
 * http://github.com/semantic-org/semantic-ui/
 *
 *
 * Released under the MIT license
 * http://opensource.org/licenses/MIT
 *
 */

(function($, window, document, undefined) {
  "use strict";

  window =
    typeof window != "undefined" && window.Math == Math
      ? window
      : typeof self != "undefined" && self.Math == Math
        ? self
        : Function("return this")();

  $.fn.visibility = function(parameters) {
    var $allModules = $(this),
      moduleSelector = $allModules.selector || "",
      time = new Date().getTime(),
      performance = [],
      query = arguments[0],
      methodInvoked = typeof query == "string",
      queryArguments = [].slice.call(arguments, 1),
      returnedValue,
      moduleCount = $allModules.length,
      loadedCount = 0;

    $allModules.each(function() {
      var settings = $.isPlainObject(parameters)
          ? $.extend(true, {}, $.fn.visibility.settings, parameters)
          : $.extend({}, $.fn.visibility.settings),
        className = settings.className,
        namespace = settings.namespace,
        error = settings.error,
        metadata = settings.metadata,
        eventNamespace = "." + namespace,
        moduleNamespace = "module-" + namespace,
        $window = $(window),
        $module = $(this),
        $context = $(settings.context),
        $placeholder,
        selector = $module.selector || "",
        instance = $module.data(moduleNamespace),
        requestAnimationFrame =
          window.requestAnimationFrame ||
          window.mozRequestAnimationFrame ||
          window.webkitRequestAnimationFrame ||
          window.msRequestAnimationFrame ||
          function(callback) {
            setTimeout(callback, 0);
          },
        element = this,
        disabled = false,
        contextObserver,
        observer,
        module;

      module = {
        initialize: function() {
          module.debug("Initializing", settings);

          module.setup.cache();

          if (module.should.trackChanges()) {
            if (settings.type == "image") {
              module.setup.image();
            }
            if (settings.type == "fixed") {
              module.setup.fixed();
            }

            if (settings.observeChanges) {
              module.observeChanges();
            }
            module.bind.events();
          }

          module.save.position();
          if (!module.is.visible()) {
            module.error(error.visible, $module);
          }

          if (settings.initialCheck) {
            module.checkVisibility();
          }
          module.instantiate();
        },

        instantiate: function() {
          module.debug("Storing instance", module);
          $module.data(moduleNamespace, module);
          instance = module;
        },

        destroy: function() {
          module.verbose("Destroying previous module");
          if (observer) {
            observer.disconnect();
          }
          if (contextObserver) {
            contextObserver.disconnect();
          }
          $window
            .off("load" + eventNamespace, module.event.load)
            .off("resize" + eventNamespace, module.event.resize);
          $context
            .off("scroll" + eventNamespace, module.event.scroll)
            .off("scrollchange" + eventNamespace, module.event.scrollchange);
          if (settings.type == "fixed") {
            module.resetFixed();
            module.remove.placeholder();
          }
          $module.off(eventNamespace).removeData(moduleNamespace);
        },

        observeChanges: function() {
          if ("MutationObserver" in window) {
            contextObserver = new MutationObserver(module.event.contextChanged);
            observer = new MutationObserver(module.event.changed);
            contextObserver.observe(document, {
              childList: true,
              subtree: true
            });
            observer.observe(element, {
              childList: true,
              subtree: true
            });
            module.debug("Setting up mutation observer", observer);
          }
        },

        bind: {
          events: function() {
            module.verbose("Binding visibility events to scroll and resize");
            if (settings.refreshOnLoad) {
              $window.on("load" + eventNamespace, module.event.load);
            }
            $window.on("resize" + eventNamespace, module.event.resize);
            // pub/sub pattern
            $context
              .off("scroll" + eventNamespace)
              .on("scroll" + eventNamespace, module.event.scroll)
              .on("scrollchange" + eventNamespace, module.event.scrollchange);
          }
        },

        event: {
          changed: function(mutations) {
            module.verbose(
              "DOM tree modified, updating visibility calculations"
            );
            module.timer = setTimeout(function() {
              module.verbose("DOM tree modified, updating sticky menu");
              module.refresh();
            }, 100);
          },
          contextChanged: function(mutations) {
            [].forEach.call(mutations, function(mutation) {
              if (mutation.removedNodes) {
                [].forEach.call(mutation.removedNodes, function(node) {
                  if (node == element || $(node).find(element).length > 0) {
                    module.debug(
                      "Element removed from DOM, tearing down events"
                    );
                    module.destroy();
                  }
                });
              }
            });
          },
          resize: function() {
            module.debug("Window resized");
            if (settings.refreshOnResize) {
              requestAnimationFrame(module.refresh);
            }
          },
          load: function() {
            module.debug("Page finished loading");
            requestAnimationFrame(module.refresh);
          },
          // publishes scrollchange event on one scroll
          scroll: function() {
            if (settings.throttle) {
              clearTimeout(module.timer);
              module.timer = setTimeout(function() {
                $context.triggerHandler("scrollchange" + eventNamespace, [
                  $context.scrollTop()
                ]);
              }, settings.throttle);
            } else {
              requestAnimationFrame(function() {
                $context.triggerHandler("scrollchange" + eventNamespace, [
                  $context.scrollTop()
                ]);
              });
            }
          },
          // subscribes to scrollchange
          scrollchange: function(event, scrollPosition) {
            module.checkVisibility(scrollPosition);
          }
        },

        precache: function(images, callback) {
          if (!(images instanceof Array)) {
            images = [images];
          }
          var imagesLength = images.length,
            loadedCounter = 0,
            cache = [],
            cacheImage = document.createElement("img"),
            handleLoad = function() {
              loadedCounter++;
              if (loadedCounter >= images.length) {
                if ($.isFunction(callback)) {
                  callback();
                }
              }
            };
          while (imagesLength--) {
            cacheImage = document.createElement("img");
            cacheImage.onload = handleLoad;
            cacheImage.onerror = handleLoad;
            cacheImage.src = images[imagesLength];
            cache.push(cacheImage);
          }
        },

        enableCallbacks: function() {
          module.debug("Allowing callbacks to occur");
          disabled = false;
        },

        disableCallbacks: function() {
          module.debug("Disabling all callbacks temporarily");
          disabled = true;
        },

        should: {
          trackChanges: function() {
            if (methodInvoked) {
              module.debug("One time query, no need to bind events");
              return false;
            }
            module.debug("Callbacks being attached");
            return true;
          }
        },

        setup: {
          cache: function() {
            module.cache = {
              occurred: {},
              screen: {},
              element: {}
            };
          },
          image: function() {
            var src = $module.data(metadata.src);
            if (src) {
              module.verbose("Lazy loading image", src);
              settings.once = true;
              settings.observeChanges = false;

              // show when top visible
              settings.onOnScreen = function() {
                module.debug("Image on screen", element);
                module.precache(src, function() {
                  module.set.image(src, function() {
                    loadedCount++;
                    if (loadedCount == moduleCount) {
                      settings.onAllLoaded.call(this);
                    }
                    settings.onLoad.call(this);
                  });
                });
              };
            }
          },
          fixed: function() {
            module.debug("Setting up fixed");
            settings.once = false;
            settings.observeChanges = false;
            settings.initialCheck = true;
            settings.refreshOnLoad = true;
            if (!parameters.transition) {
              settings.transition = false;
            }
            module.create.placeholder();
            module.debug("Added placeholder", $placeholder);
            settings.onTopPassed = function() {
              module.debug("Element passed, adding fixed position", $module);
              module.show.placeholder();
              module.set.fixed();
              if (settings.transition) {
                if ($.fn.transition !== undefined) {
                  $module.transition(settings.transition, settings.duration);
                }
              }
            };
            settings.onTopPassedReverse = function() {
              module.debug(
                "Element returned to position, removing fixed",
                $module
              );
              module.hide.placeholder();
              module.remove.fixed();
            };
          }
        },

        create: {
          placeholder: function() {
            module.verbose("Creating fixed position placeholder");
            $placeholder = $module
              .clone(false)
              .css("display", "none")
              .addClass(className.placeholder)
              .insertAfter($module);
          }
        },

        show: {
          placeholder: function() {
            module.verbose("Showing placeholder");
            $placeholder.css("display", "block").css("visibility", "hidden");
          }
        },
        hide: {
          placeholder: function() {
            module.verbose("Hiding placeholder");
            $placeholder.css("display", "none").css("visibility", "");
          }
        },

        set: {
          fixed: function() {
            module.verbose("Setting element to fixed position");
            $module.addClass(className.fixed).css({
              position: "fixed",
              top: settings.offset + "px",
              left: "auto",
              zIndex: settings.zIndex
            });
            settings.onFixed.call(element);
          },
          image: function(src, callback) {
            $module.attr("src", src);
            if (settings.transition) {
              if ($.fn.transition !== undefined) {
                if ($module.hasClass(className.visible)) {
                  module.debug(
                    "Transition already occurred on this image, skipping animation"
                  );
                  return;
                }
                $module.transition(
                  settings.transition,
                  settings.duration,
                  callback
                );
              } else {
                $module.fadeIn(settings.duration, callback);
              }
            } else {
              $module.show();
            }
          }
        },

        is: {
          onScreen: function() {
            var calculations = module.get.elementCalculations();
            return calculations.onScreen;
          },
          offScreen: function() {
            var calculations = module.get.elementCalculations();
            return calculations.offScreen;
          },
          visible: function() {
            if (module.cache && module.cache.element) {
              return !(
                module.cache.element.width === 0 &&
                module.cache.element.offset.top === 0
              );
            }
            return false;
          },
          verticallyScrollableContext: function() {
            var overflowY =
              $context.get(0) !== window ? $context.css("overflow-y") : false;
            return overflowY == "auto" || overflowY == "scroll";
          },
          horizontallyScrollableContext: function() {
            var overflowX =
              $context.get(0) !== window ? $context.css("overflow-x") : false;
            return overflowX == "auto" || overflowX == "scroll";
          }
        },

        refresh: function() {
          module.debug("Refreshing constants (width/height)");
          if (settings.type == "fixed") {
            module.resetFixed();
          }
          module.reset();
          module.save.position();
          if (settings.checkOnRefresh) {
            module.checkVisibility();
          }
          settings.onRefresh.call(element);
        },

        resetFixed: function() {
          module.remove.fixed();
          module.remove.occurred();
        },

        reset: function() {
          module.verbose("Resetting all cached values");
          if ($.isPlainObject(module.cache)) {
            module.cache.screen = {};
            module.cache.element = {};
          }
        },

        checkVisibility: function(scroll) {
          module.verbose(
            "Checking visibility of element",
            module.cache.element
          );

          if (!disabled && module.is.visible()) {
            // save scroll position
            module.save.scroll(scroll);

            // update calculations derived from scroll
            module.save.calculations();

            // percentage
            module.passed();

            // reverse (must be first)
            module.passingReverse();
            module.topVisibleReverse();
            module.bottomVisibleReverse();
            module.topPassedReverse();
            module.bottomPassedReverse();

            // one time
            module.onScreen();
            module.offScreen();
            module.passing();
            module.topVisible();
            module.bottomVisible();
            module.topPassed();
            module.bottomPassed();

            // on update callback
            if (settings.onUpdate) {
              settings.onUpdate.call(element, module.get.elementCalculations());
            }
          }
        },

        passed: function(amount, newCallback) {
          var calculations = module.get.elementCalculations(),
            amountInPixels;
          // assign callback
          if (amount && newCallback) {
            settings.onPassed[amount] = newCallback;
          } else if (amount !== undefined) {
            return module.get.pixelsPassed(amount) > calculations.pixelsPassed;
          } else if (calculations.passing) {
            $.each(settings.onPassed, function(amount, callback) {
              if (
                calculations.bottomVisible ||
                calculations.pixelsPassed > module.get.pixelsPassed(amount)
              ) {
                module.execute(callback, amount);
              } else if (!settings.once) {
                module.remove.occurred(callback);
              }
            });
          }
        },

        onScreen: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onOnScreen,
            callbackName = "onScreen";
          if (newCallback) {
            module.debug("Adding callback for onScreen", newCallback);
            settings.onOnScreen = newCallback;
          }
          if (calculations.onScreen) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback !== undefined) {
            return calculations.onOnScreen;
          }
        },

        offScreen: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onOffScreen,
            callbackName = "offScreen";
          if (newCallback) {
            module.debug("Adding callback for offScreen", newCallback);
            settings.onOffScreen = newCallback;
          }
          if (calculations.offScreen) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback !== undefined) {
            return calculations.onOffScreen;
          }
        },

        passing: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onPassing,
            callbackName = "passing";
          if (newCallback) {
            module.debug("Adding callback for passing", newCallback);
            settings.onPassing = newCallback;
          }
          if (calculations.passing) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback !== undefined) {
            return calculations.passing;
          }
        },

        topVisible: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onTopVisible,
            callbackName = "topVisible";
          if (newCallback) {
            module.debug("Adding callback for top visible", newCallback);
            settings.onTopVisible = newCallback;
          }
          if (calculations.topVisible) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return calculations.topVisible;
          }
        },

        bottomVisible: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onBottomVisible,
            callbackName = "bottomVisible";
          if (newCallback) {
            module.debug("Adding callback for bottom visible", newCallback);
            settings.onBottomVisible = newCallback;
          }
          if (calculations.bottomVisible) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return calculations.bottomVisible;
          }
        },

        topPassed: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onTopPassed,
            callbackName = "topPassed";
          if (newCallback) {
            module.debug("Adding callback for top passed", newCallback);
            settings.onTopPassed = newCallback;
          }
          if (calculations.topPassed) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return calculations.topPassed;
          }
        },

        bottomPassed: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onBottomPassed,
            callbackName = "bottomPassed";
          if (newCallback) {
            module.debug("Adding callback for bottom passed", newCallback);
            settings.onBottomPassed = newCallback;
          }
          if (calculations.bottomPassed) {
            module.execute(callback, callbackName);
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return calculations.bottomPassed;
          }
        },

        passingReverse: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onPassingReverse,
            callbackName = "passingReverse";
          if (newCallback) {
            module.debug("Adding callback for passing reverse", newCallback);
            settings.onPassingReverse = newCallback;
          }
          if (!calculations.passing) {
            if (module.get.occurred("passing")) {
              module.execute(callback, callbackName);
            }
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback !== undefined) {
            return !calculations.passing;
          }
        },

        topVisibleReverse: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onTopVisibleReverse,
            callbackName = "topVisibleReverse";
          if (newCallback) {
            module.debug(
              "Adding callback for top visible reverse",
              newCallback
            );
            settings.onTopVisibleReverse = newCallback;
          }
          if (!calculations.topVisible) {
            if (module.get.occurred("topVisible")) {
              module.execute(callback, callbackName);
            }
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return !calculations.topVisible;
          }
        },

        bottomVisibleReverse: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onBottomVisibleReverse,
            callbackName = "bottomVisibleReverse";
          if (newCallback) {
            module.debug(
              "Adding callback for bottom visible reverse",
              newCallback
            );
            settings.onBottomVisibleReverse = newCallback;
          }
          if (!calculations.bottomVisible) {
            if (module.get.occurred("bottomVisible")) {
              module.execute(callback, callbackName);
            }
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return !calculations.bottomVisible;
          }
        },

        topPassedReverse: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onTopPassedReverse,
            callbackName = "topPassedReverse";
          if (newCallback) {
            module.debug("Adding callback for top passed reverse", newCallback);
            settings.onTopPassedReverse = newCallback;
          }
          if (!calculations.topPassed) {
            if (module.get.occurred("topPassed")) {
              module.execute(callback, callbackName);
            }
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return !calculations.onTopPassed;
          }
        },

        bottomPassedReverse: function(newCallback) {
          var calculations = module.get.elementCalculations(),
            callback = newCallback || settings.onBottomPassedReverse,
            callbackName = "bottomPassedReverse";
          if (newCallback) {
            module.debug(
              "Adding callback for bottom passed reverse",
              newCallback
            );
            settings.onBottomPassedReverse = newCallback;
          }
          if (!calculations.bottomPassed) {
            if (module.get.occurred("bottomPassed")) {
              module.execute(callback, callbackName);
            }
          } else if (!settings.once) {
            module.remove.occurred(callbackName);
          }
          if (newCallback === undefined) {
            return !calculations.bottomPassed;
          }
        },

        execute: function(callback, callbackName) {
          var calculations = module.get.elementCalculations(),
            screen = module.get.screenCalculations();
          callback = callback || false;
          if (callback) {
            if (settings.continuous) {
              module.debug(
                "Callback being called continuously",
                callbackName,
                calculations
              );
              callback.call(element, calculations, screen);
            } else if (!module.get.occurred(callbackName)) {
              module.debug("Conditions met", callbackName, calculations);
              callback.call(element, calculations, screen);
            }
          }
          module.save.occurred(callbackName);
        },

        remove: {
          fixed: function() {
            module.debug("Removing fixed position");
            $module.removeClass(className.fixed).css({
              position: "",
              top: "",
              left: "",
              zIndex: ""
            });
            settings.onUnfixed.call(element);
          },
          placeholder: function() {
            module.debug("Removing placeholder content");
            if ($placeholder) {
              $placeholder.remove();
            }
          },
          occurred: function(callback) {
            if (callback) {
              var occurred = module.cache.occurred;
              if (
                occurred[callback] !== undefined &&
                occurred[callback] === true
              ) {
                module.debug("Callback can now be called again", callback);
                module.cache.occurred[callback] = false;
              }
            } else {
              module.cache.occurred = {};
            }
          }
        },

        save: {
          calculations: function() {
            module.verbose(
              "Saving all calculations necessary to determine positioning"
            );
            module.save.direction();
            module.save.screenCalculations();
            module.save.elementCalculations();
          },
          occurred: function(callback) {
            if (callback) {
              if (
                module.cache.occurred[callback] === undefined ||
                module.cache.occurred[callback] !== true
              ) {
                module.verbose("Saving callback occurred", callback);
                module.cache.occurred[callback] = true;
              }
            }
          },
          scroll: function(scrollPosition) {
            scrollPosition =
              scrollPosition + settings.offset ||
              $context.scrollTop() + settings.offset;
            module.cache.scroll = scrollPosition;
          },
          direction: function() {
            var scroll = module.get.scroll(),
              lastScroll = module.get.lastScroll(),
              direction;
            if (scroll > lastScroll && lastScroll) {
              direction = "down";
            } else if (scroll < lastScroll && lastScroll) {
              direction = "up";
            } else {
              direction = "static";
            }
            module.cache.direction = direction;
            return module.cache.direction;
          },
          elementPosition: function() {
            var element = module.cache.element,
              screen = module.get.screenSize();
            module.verbose("Saving element position");
            // (quicker than $.extend)
            element.fits = element.height < screen.height;
            element.offset = $module.offset();
            element.width = $module.outerWidth();
            element.height = $module.outerHeight();
            // compensate for scroll in context
            if (module.is.verticallyScrollableContext()) {
              element.offset.top +=
                $context.scrollTop() - $context.offset().top;
            }
            if (module.is.horizontallyScrollableContext()) {
              element.offset.left +=
                $context.scrollLeft - $context.offset().left;
            }
            // store
            module.cache.element = element;
            return element;
          },
          elementCalculations: function() {
            var screen = module.get.screenCalculations(),
              element = module.get.elementPosition();
            // offset
            if (settings.includeMargin) {
              element.margin = {};
              element.margin.top = parseInt($module.css("margin-top"), 10);
              element.margin.bottom = parseInt(
                $module.css("margin-bottom"),
                10
              );
              element.top = element.offset.top - element.margin.top;
              element.bottom =
                element.offset.top + element.height + element.margin.bottom;
            } else {
              element.top = element.offset.top;
              element.bottom = element.offset.top + element.height;
            }

            // visibility
            element.topPassed = screen.top >= element.top;
            element.bottomPassed = screen.top >= element.bottom;
            element.topVisible =
              screen.bottom >= element.top && !element.topPassed;
            element.bottomVisible =
              screen.bottom >= element.bottom && !element.bottomPassed;
            element.pixelsPassed = 0;
            element.percentagePassed = 0;

            // meta calculations
            element.onScreen =
              (element.topVisible || element.passing) && !element.bottomPassed;
            element.passing = element.topPassed && !element.bottomPassed;
            element.offScreen = !element.onScreen;

            // passing calculations
            if (element.passing) {
              element.pixelsPassed = screen.top - element.top;
              element.percentagePassed =
                (screen.top - element.top) / element.height;
            }
            module.cache.element = element;
            module.verbose("Updated element calculations", element);
            return element;
          },
          screenCalculations: function() {
            var scroll = module.get.scroll();
            module.save.direction();
            module.cache.screen.top = scroll;
            module.cache.screen.bottom = scroll + module.cache.screen.height;
            return module.cache.screen;
          },
          screenSize: function() {
            module.verbose("Saving window position");
            module.cache.screen = {
              height: $context.height()
            };
          },
          position: function() {
            module.save.screenSize();
            module.save.elementPosition();
          }
        },

        get: {
          pixelsPassed: function(amount) {
            var element = module.get.elementCalculations();
            if (amount.search("%") > -1) {
              return element.height * (parseInt(amount, 10) / 100);
            }
            return parseInt(amount, 10);
          },
          occurred: function(callback) {
            return module.cache.occurred !== undefined
              ? module.cache.occurred[callback] || false
              : false;
          },
          direction: function() {
            if (module.cache.direction === undefined) {
              module.save.direction();
            }
            return module.cache.direction;
          },
          elementPosition: function() {
            if (module.cache.element === undefined) {
              module.save.elementPosition();
            }
            return module.cache.element;
          },
          elementCalculations: function() {
            if (module.cache.element === undefined) {
              module.save.elementCalculations();
            }
            return module.cache.element;
          },
          screenCalculations: function() {
            if (module.cache.screen === undefined) {
              module.save.screenCalculations();
            }
            return module.cache.screen;
          },
          screenSize: function() {
            if (module.cache.screen === undefined) {
              module.save.screenSize();
            }
            return module.cache.screen;
          },
          scroll: function() {
            if (module.cache.scroll === undefined) {
              module.save.scroll();
            }
            return module.cache.scroll;
          },
          lastScroll: function() {
            if (module.cache.screen === undefined) {
              module.debug("First scroll event, no last scroll could be found");
              return false;
            }
            return module.cache.screen.top;
          }
        },

        setting: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, settings, name);
          } else if (value !== undefined) {
            settings[name] = value;
          } else {
            return settings[name];
          }
        },
        internal: function(name, value) {
          if ($.isPlainObject(name)) {
            $.extend(true, module, name);
          } else if (value !== undefined) {
            module[name] = value;
          } else {
            return module[name];
          }
        },
        debug: function() {
          if (!settings.silent && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.debug = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.debug.apply(console, arguments);
            }
          }
        },
        verbose: function() {
          if (!settings.silent && settings.verbose && settings.debug) {
            if (settings.performance) {
              module.performance.log(arguments);
            } else {
              module.verbose = Function.prototype.bind.call(
                console.info,
                console,
                settings.name + ":"
              );
              module.verbose.apply(console, arguments);
            }
          }
        },
        error: function() {
          if (!settings.silent) {
            module.error = Function.prototype.bind.call(
              console.error,
              console,
              settings.name + ":"
            );
            module.error.apply(console, arguments);
          }
        },
        performance: {
          log: function(message) {
            var currentTime, executionTime, previousTime;
            if (settings.performance) {
              currentTime = new Date().getTime();
              previousTime = time || currentTime;
              executionTime = currentTime - previousTime;
              time = currentTime;
              performance.push({
                Name: message[0],
                Arguments: [].slice.call(message, 1) || "",
                Element: element,
                "Execution Time": executionTime
              });
            }
            clearTimeout(module.performance.timer);
            module.performance.timer = setTimeout(
              module.performance.display,
              500
            );
          },
          display: function() {
            var title = settings.name + ":",
              totalTime = 0;
            time = false;
            clearTimeout(module.performance.timer);
            $.each(performance, function(index, data) {
              totalTime += data["Execution Time"];
            });
            title += " " + totalTime + "ms";
            if (moduleSelector) {
              title += " '" + moduleSelector + "'";
            }
            if (
              (console.group !== undefined || console.table !== undefined) &&
              performance.length > 0
            ) {
              console.groupCollapsed(title);
              if (console.table) {
                console.table(performance);
              } else {
                $.each(performance, function(index, data) {
                  console.log(
                    data["Name"] + ": " + data["Execution Time"] + "ms"
                  );
                });
              }
              console.groupEnd();
            }
            performance = [];
          }
        },
        invoke: function(query, passedArguments, context) {
          var object = instance,
            maxDepth,
            found,
            response;
          passedArguments = passedArguments || queryArguments;
          context = element || context;
          if (typeof query == "string" && object !== undefined) {
            query = query.split(/[\. ]/);
            maxDepth = query.length - 1;
            $.each(query, function(depth, value) {
              var camelCaseValue =
                depth != maxDepth
                  ? value +
                    query[depth + 1].charAt(0).toUpperCase() +
                    query[depth + 1].slice(1)
                  : query;
              if (
                $.isPlainObject(object[camelCaseValue]) &&
                depth != maxDepth
              ) {
                object = object[camelCaseValue];
              } else if (object[camelCaseValue] !== undefined) {
                found = object[camelCaseValue];
                return false;
              } else if ($.isPlainObject(object[value]) && depth != maxDepth) {
                object = object[value];
              } else if (object[value] !== undefined) {
                found = object[value];
                return false;
              } else {
                module.error(error.method, query);
                return false;
              }
            });
          }
          if ($.isFunction(found)) {
            response = found.apply(context, passedArguments);
          } else if (found !== undefined) {
            response = found;
          }
          if ($.isArray(returnedValue)) {
            returnedValue.push(response);
          } else if (returnedValue !== undefined) {
            returnedValue = [returnedValue, response];
          } else if (response !== undefined) {
            returnedValue = response;
          }
          return found;
        }
      };

      if (methodInvoked) {
        if (instance === undefined) {
          module.initialize();
        }
        instance.save.scroll();
        instance.save.calculations();
        module.invoke(query);
      } else {
        if (instance !== undefined) {
          instance.invoke("destroy");
        }
        module.initialize();
      }
    });

    return returnedValue !== undefined ? returnedValue : this;
  };

  $.fn.visibility.settings = {
    name: "Visibility",
    namespace: "visibility",

    debug: false,
    verbose: false,
    performance: true,

    // whether to use mutation observers to follow changes
    observeChanges: true,

    // check position immediately on init
    initialCheck: true,

    // whether to refresh calculations after all page images load
    refreshOnLoad: true,

    // whether to refresh calculations after page resize event
    refreshOnResize: true,

    // should call callbacks on refresh event (resize, etc)
    checkOnRefresh: true,

    // callback should only occur one time
    once: true,

    // callback should fire continuously whe evaluates to true
    continuous: false,

    // offset to use with scroll top
    offset: 0,

    // whether to include margin in elements position
    includeMargin: false,

    // scroll context for visibility checks
    context: window,

    // visibility check delay in ms (defaults to animationFrame)
    throttle: false,

    // special visibility type (image, fixed)
    type: false,

    // z-index to use with visibility 'fixed'
    zIndex: "10",

    // image only animation settings
    transition: "fade in",
    duration: 1000,

    // array of callbacks for percentage
    onPassed: {},

    // standard callbacks
    onOnScreen: false,
    onOffScreen: false,
    onPassing: false,
    onTopVisible: false,
    onBottomVisible: false,
    onTopPassed: false,
    onBottomPassed: false,

    // reverse callbacks
    onPassingReverse: false,
    onTopVisibleReverse: false,
    onBottomVisibleReverse: false,
    onTopPassedReverse: false,
    onBottomPassedReverse: false,

    // special callbacks for image
    onLoad: function() {},
    onAllLoaded: function() {},

    // special callbacks for fixed position
    onFixed: function() {},
    onUnfixed: function() {},

    // utility callbacks
    onUpdate: false, // disabled by default for performance
    onRefresh: function() {},

    metadata: {
      src: "src"
    },

    className: {
      fixed: "fixed",
      placeholder: "placeholder",
      visible: "visible"
    },

    error: {
      method: "The method you called is not defined.",
      visible:
        "Element is hidden, you must call refresh after element becomes visible"
    }
  };
})(jQuery, window, document);
